/*
 * drivers/input/keyboard/lamp_board.c
 *
 *
 */
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/pwm.h>

#define COMPATILE_NAME "lamp_board"

#define MAX_MODE 3
#define MAX_LEVEL 6
#define MODE_GPIO_NAME "mode-gpios"
#define EN_GPIO_NAME "en-gpios"
#define NIGHT_GPIO_NAME "night-gpios"
#define LEVEL_NAME "levels"

#define LOGI(fmt, args...)    pr_info(COMPATILE_NAME":%s@%d: "fmt, __FUNCTION__, __LINE__, ##args)
#define LOGD(fmt, args...)    pr_dbg(COMPATILE_NAME":%s@%d: "fmt, __FUNCTION__, __LINE__, ##args)

#define OP_LAMP_EN 0
#define OP_TOGGLE_NIGHT 1
#define OP_TOGGLE_MODE 2
#define OP_SET_LEVEL0 3
#define OP_SET_LEVEL1 4
#define OP_SET_LEVEL2 5
#define OP_SET_LEVEL3 6
#define OP_SET_LEVEL4 7
#define OP_SET_LEVEL5 8
#define OP_SET_LEVEL5 8
#define OP_MAX  (OP_SET_LEVEL5 + 1)

/* DTS进行如下的配置

{
	lamp_board: lamp_board {
		compatible = "lamp_board";
		mode-gpios = <&extgpio 0 GPIO_ACTIVE_HIGH
						&extgpio 1 GPIO_ACTIVE_HIGH >;
	//	en-gpios = <&gpio1 11 GPIO_ACTIVE_HIGH>;
		night-gpios = <&extgpio 11 GPIO_ACTIVE_HIGH>;
		levels = < 0 30 60 156 188 255 >;
        pwms = <&pwm1 0 25000>;
        max-brightness = <255>;
	};
}
 */

#define LED_CAT_NIGHT 0
#define LED_CAT_STUDY 1
#define LED_CAT_MAX (LED_CAT_STUDY+1)


static int flag_lock = 0;
static int def_levels[MAX_LEVEL] = {0, 32, 64, 128, 168, 255};
module_param_array(def_levels, int, NULL, 0644);

struct lamp_board {
	struct device *dev;
	int mode_gpio[MAX_MODE];
	int nmode;  // 模式gpio的数量
	int cmode; // 当前模式
	int night_gpio;
	int night; // 夜间模式的状态
	int en_gpio;
	int enable;
	struct pwm_device	*pwm;
	int max_brightness;
	int period;
	int levels[MAX_LEVEL];
	int nlevel;  // 亮度等级数量
	int clevel; // 当前亮度等级
	int brightness; // 实际亮度值，范围为 0~255
	int status[LED_CAT_MAX]; //
};

static struct lamp_board *gboard;


/**
 * 控制夜灯的开关
 * @param en [0 关闭夜灯， 1 打开夜灯]
 */
void lamp_night_en(int en)
{
	if(flag_lock) return;
	gboard->night = en;
	if(gpio_is_valid(gboard->night_gpio)){
		gpio_set_value_cansleep(gboard->night_gpio, gboard->night);
	}
}


static void lamp_night_toggle(void)
{
	lamp_night_en(!gboard->night);
}




static void lamp_set_mode(int mode)
{
	LOGI("mode=%d\n", mode);
	gboard->cmode = mode;
	switch(mode){
	case 0:
		gpio_set_value_cansleep(gboard->mode_gpio[0], 1);
		gpio_set_value_cansleep(gboard->mode_gpio[1], 0);
		break;
	case 1:
		gpio_set_value_cansleep(gboard->mode_gpio[0], 0);
		gpio_set_value_cansleep(gboard->mode_gpio[1], 1);
		break;
	case 2:
		gpio_set_value_cansleep(gboard->mode_gpio[0], 1);
		gpio_set_value_cansleep(gboard->mode_gpio[1], 1);
		break;
	}
}

static void lamp_set_mode2(int mode)
{
	LOGI("mode=%d\n", mode);
	switch(mode){
	case 0:
		gpio_set_value_cansleep(gboard->mode_gpio[0], 1);
		gpio_set_value_cansleep(gboard->mode_gpio[1], 0);
		gpio_set_value_cansleep(gboard->night_gpio, 0);
		break;
	case 1:
		gpio_set_value_cansleep(gboard->mode_gpio[0], 0);
		gpio_set_value_cansleep(gboard->mode_gpio[1], 1);
		gpio_set_value_cansleep(gboard->night_gpio, 0);
		break;
	case 2:
		gpio_set_value_cansleep(gboard->mode_gpio[0], 1);
		gpio_set_value_cansleep(gboard->mode_gpio[1], 1);
		gpio_set_value_cansleep(gboard->night_gpio, 0);
		break;
	case 3:
		gpio_set_value_cansleep(gboard->mode_gpio[0], 0);
		gpio_set_value_cansleep(gboard->mode_gpio[1], 0);
		gpio_set_value_cansleep(gboard->night_gpio, 1);
		break;
	default:
		break;
	}
}
static void lamp_mode_looper(void)
{
	gboard->cmode +=1;
	if(gboard->cmode >= MAX_MODE){
		gboard->cmode = 0;
	}
	lamp_set_mode(gboard->cmode);
	LOGI("cmode=%d\n", gboard->cmode);
}

static void _lamp_set_level(int level)
{
	unsigned int max = gboard->max_brightness;
	unsigned int period =  gboard->period;
	int duty;
	duty = level * period / max;
	LOGI(" level=%d, duty=%d\n", level, duty);
	gboard->brightness = level; // 存储亮度值
	if(IS_ERR(gboard->pwm)){
		pr_err("%s pwm is invalid\n", __func__);
		return;
	}
	pwm_config(gboard->pwm, duty, gboard->period);
	if (duty == 0)
		pwm_disable(gboard->pwm);
	else
		pwm_enable(gboard->pwm);
}

static void lamp_set_level(int lv)
{
	gboard->clevel = lv;
	_lamp_set_level(gboard->levels[lv]);
}

static void lamp_switch(int en)
{
	int i = 0;
	gboard->enable = en;
	if(gboard->enable){
		// restore state
		lamp_set_mode(gboard->cmode);
		lamp_set_level(gboard->clevel);
		gpio_set_value_cansleep(gboard->night_gpio, gboard->night);
	}else {
		for(i=0; i<gboard->nmode; i++){
			gpio_set_value_cansleep(gboard->mode_gpio[i], 0);
		}
		pwm_disable(gboard->pwm);
//		gpio_set_value_cansleep(gboard->night_gpio, 0);
	}
	LOGI("enable=%d\n", gboard->enable);
	if(gpio_is_valid(gboard->en_gpio)){
		gpio_set_value_cansleep(gboard->en_gpio, gboard->enable);
	}
}

static void lamp_en(void)
{
	int i = 0;
	gboard->enable = !gboard->enable;
	if(gboard->enable){
		// restore state
		lamp_set_mode(gboard->cmode);
		lamp_set_level(gboard->clevel);
	//	gpio_set_value_cansleep(gboard->night_gpio, gboard->night);
	}else {
		for(i=0; i<gboard->nmode; i++){
			gpio_set_value_cansleep(gboard->mode_gpio[i], 0);
		}
		pwm_disable(gboard->pwm);
	//	gpio_set_value_cansleep(gboard->night_gpio, 0);
	}
	pr_info("%s status=%d\n", __func__, gboard->enable);
	if(gpio_is_valid(gboard->en_gpio)){
		gpio_set_value_cansleep(gboard->en_gpio, gboard->enable);
	}
}

/**
 * 控制夜灯的开关
 * @param en [0 关闭夜灯， 1 打开夜灯]
 */
void lamp_study_en(int en)
{
	if(flag_lock) return;
	gboard->enable = en;
	if(en){
		gpio_set_value_cansleep(gboard->mode_gpio[0], 1);
		gpio_set_value_cansleep(gboard->mode_gpio[1], 1);
		lamp_set_level(gboard->clevel);
	}else{
		gpio_set_value_cansleep(gboard->mode_gpio[0], 0);
		gpio_set_value_cansleep(gboard->mode_gpio[1], 0);
		pwm_disable(gboard->pwm);
	}
}


static void lamp_study_toggle(void)
{

	lamp_study_en(!gboard->enable);
}


void led_force_off(int off)
{
	if(off){
		lamp_study_en(0);
		lamp_night_en(0);
		flag_lock = 1;
	}else {
		flag_lock = 0;
	}
}

void lamp_controller(int op)
{
	if(!gboard){
		pr_err("Lambp board not init yet\n");
		return ;
	}
	if(op>=OP_SET_LEVEL0 && op<=OP_SET_LEVEL5){
		lamp_set_level(op - OP_SET_LEVEL0);

	}else{
		switch(op){
			case OP_LAMP_EN:
				lamp_study_toggle();
				break;
			case OP_TOGGLE_MODE:    // no mode choose, change it to night mode
//				lamp_mode_looper();
//				break;
//			case OP_TOGGLE_NIGHT:
				lamp_night_toggle();
				break;
			default:
				if(op >= OP_MAX ){
					_lamp_set_level(op);
				}
				break;
		}
	}
}

EXPORT_SYMBOL(lamp_controller);

static int st7710s_parse_dt(struct device *dev)
{
	int ret = 0;
	struct device_node *np ;
	struct lamp_board *lamp_board;
	int cnt, i;
	char buf[64];

	if(dev){
		np = dev->of_node;
	  	lamp_board = dev_get_drvdata(dev);
	}

	if(!np || !lamp_board){
		dev_err(dev, "no of_node found or lamp_board NULL\n");
		return -ENODEV;
	}
	if((ret=of_get_named_gpio(np, EN_GPIO_NAME, 0)) < 0){
		dev_err(dev, "of_get_named_gpio: %s fail, err=%d\n", EN_GPIO_NAME, ret);
		if(ret == -EPROBE_DEFER){
			return -EPROBE_DEFER;
		}
	}
	lamp_board->en_gpio = ret;

	if((ret=of_get_named_gpio(np, NIGHT_GPIO_NAME, 0)) < 0){
		dev_err(dev, "of_get_named_gpio: %s fail, err=%d\n", NIGHT_GPIO_NAME, ret);
		if(ret == -EPROBE_DEFER){
			return -EPROBE_DEFER;
		}
	}
	lamp_board->night_gpio = ret;

	cnt = of_gpio_named_count(np, MODE_GPIO_NAME);
	if(cnt > MAX_MODE){
		cnt = MAX_MODE;
	}
	lamp_board->nmode = cnt;
	for(i=0; i<cnt; i++){
		if((ret=of_get_named_gpio(np, MODE_GPIO_NAME, i)) < 0){
			dev_err(dev, "of_get_named_gpio: %s fail, err=%d\n", MODE_GPIO_NAME, ret);
			if(ret == -EPROBE_DEFER){
				return -EPROBE_DEFER;
			}
		}
		lamp_board->mode_gpio[i] = ret;
	}

	if(gpio_is_valid(lamp_board->night_gpio)){
		gpio_request(lamp_board->night_gpio , NIGHT_GPIO_NAME);
		gpio_direction_output(lamp_board->night_gpio, 0);
	}
	if(gpio_is_valid(lamp_board->en_gpio)){
		gpio_request(lamp_board->en_gpio , EN_GPIO_NAME);
		gpio_direction_output(lamp_board->en_gpio, 0);
	}

	for(i=0; i<cnt; i++){
		if(gpio_is_valid(lamp_board->mode_gpio[i])){
			gpio_request(lamp_board->mode_gpio[i] , "lamp_mode");
			gpio_direction_output(lamp_board->mode_gpio[i], 0);
		}
	}
#if 0
	cnt = of_property_count_u32_elems(np, LEVEL_NAME);
#else
	cnt = MAX_LEVEL;
#endif
	if(cnt <= 0){
		dev_err(dev, "read %s failed, err=%d\n", LEVEL_NAME, cnt);
	}else{
		if(cnt > MAX_LEVEL){
			cnt = MAX_LEVEL;
		}
		lamp_board->nlevel = cnt;
		ret = of_property_read_u32_array(np, LEVEL_NAME, lamp_board->levels, lamp_board->nlevel);
		if(ret < 0) {
			dev_err(dev, "read %s with %d elems failed, err=%d\n", LEVEL_NAME, cnt, ret);
		}
	}
	cnt = sprintf(buf, "level: ");
	for(i=0; i<lamp_board->nlevel; i++){
		cnt += sprintf(buf+cnt, "%d ", lamp_board->levels[i]);
	}
	sprintf(buf+cnt, "\n");
	dev_info(dev, "%s", buf);

// pwm usage
	lamp_board->pwm = devm_of_pwm_get(dev, np, "lamp_pwm");
	if (IS_ERR(lamp_board->pwm)) {
		ret = PTR_ERR(lamp_board->pwm);
		dev_err(dev,"unable to request PWM for %s, err=%d\n", COMPATILE_NAME, ret);
	}else{
		lamp_board->period = pwm_get_period(lamp_board->pwm);
		of_property_read_u32(np, "max-brightness",&lamp_board->max_brightness);
	}
	return 0;
}

static ssize_t lamp_board_show(struct device *dev,struct device_attribute *attr, char *buf)
{
	int n=0;
//	struct lamp_board * lb = dev_get_drvdata(dev);
	dev_info(dev, "%s\n", __func__);
	n = sprintf(buf, "Usage: echo 0~8 > debug\n");
	return n;
}

static ssize_t lamp_board_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t len)
{
	int op, n;
//	struct lamp_board * lb = dev_get_drvdata(dev);
	dev_info(dev, "%s buf=%s\n", __func__, buf);
	n = sscanf(buf, "%d", &op);
	if(n != 1){
		dev_err(dev, "Usage: echo 0~8 > debug\n");
	}else{
		lamp_controller(op);
	}
	return len;
}

//
// echo mode 0|1|2|3 > /sys/bus/platform/devices/lamp_board.23/control
// 0 阅读模式 1 休闲模式 2 工作模式 3 夜灯模式
// echo on/off 0 > /sys/bus/platform/devices/lamp_board.23/control
// echo bright n > /sys/bus/platform/devices/lamp_board.23/control
// n为亮度值，0~255

static ssize_t lamp_board_ctl_show(struct device *dev,struct device_attribute *attr, char *buf)
{
	int n=0;
	struct lamp_board * lb = dev_get_drvdata(dev);
	dev_info(dev, "%s\n", __func__);
	n = sprintf(buf, "%d %d %d\n",  lb->enable, lb->night, lb->brightness);
	return n;
}

static ssize_t lamp_board_ctl_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t len)
{
	int val, n;
	char cmd[16];
//	struct lamp_board * lb = dev_get_drvdata(dev);
	memset(cmd, 0, sizeof(cmd));
	dev_info(dev, "%s buf=%s\n", __func__, buf);
	n = sscanf(buf, "%s %d", cmd, &val);
	if(n==2){
		if(!strncmp(cmd, "on", 2)){
			if(val == 0){
				lamp_study_en(1);
			}else if(val == 1){
				lamp_night_en(1);
			}
		}else if(!strncmp(cmd, "off", 3)){
			if(val == 0){
				lamp_study_en(0);
			}else if(val == 1){
				lamp_night_en(0);
			}
		}else if(!strncmp(cmd, "mode", 4)){
			lamp_set_mode2(val);
		}else if(!strncmp(cmd, "bright", 6)){
			_lamp_set_level(val);
		}else {
			dev_info(dev, "bad command: %s\n", cmd);
		}
	}
	return len;
}

static DEVICE_ATTR(debug, 0644, lamp_board_show, lamp_board_store);
static DEVICE_ATTR(control, 0644, lamp_board_ctl_show, lamp_board_ctl_store);
static int lamp_board_probe(struct platform_device *pdev)
{
	struct lamp_board *lamp_board;
	int i, err;
	lamp_board = kzalloc(sizeof(struct lamp_board), GFP_KERNEL);
	if(!lamp_board){
		dev_err(&pdev->dev, "kzalloc failed\n");
		return -ENOMEM;
	}
	gboard = lamp_board;
	lamp_board->nlevel = MAX_LEVEL;
	for(i=0; i<lamp_board->nlevel; i++){
		lamp_board->levels[i] = def_levels[i];
	}
	lamp_board->clevel = 1;
	lamp_board->cmode = 0;
	platform_set_drvdata(pdev, lamp_board);
	lamp_board->dev = &pdev->dev;
	err = st7710s_parse_dt(&pdev->dev);
	if(err <0 ){
		return err;
	}
	err = device_create_file(&pdev->dev, &dev_attr_debug);
	if(err < 0){
		dev_err(&pdev->dev, "device_create_file failed err=%d\n", err);
	}
	err = device_create_file(&pdev->dev, &dev_attr_control);
	if(err < 0){
		dev_err(&pdev->dev, "device_create_file failed err=%d\n", err);
	}
	return err;
};

static int lamp_board_remove(struct platform_device *pdev)
{
	int i, gpio;
	struct lamp_board *lamp_board = platform_get_drvdata(pdev);

	for(i=0; i<lamp_board->nmode; i++){
		gpio = lamp_board->mode_gpio[i];
		if(gpio_is_valid(gpio)){
			gpio_free(gpio);
		}
	}
	gpio = lamp_board->en_gpio;
	if(gpio_is_valid(gpio)){
		gpio_free(gpio);
	}
	gpio = lamp_board->night_gpio;
	if(gpio_is_valid(gpio)){
		gpio_free(gpio);
	}

	platform_set_drvdata(pdev, NULL);
	kfree(lamp_board);
	return 0;
}

static const struct of_device_id lamp_board_of_match[] = {
	{ .compatible = COMPATILE_NAME, },
	{},
};

static struct platform_driver lamp_board_driver = {
	.driver  = {
		.name    = "lamp_board",
		.owner	= THIS_MODULE,
		.of_match_table = of_match_ptr(lamp_board_of_match)
	 },
	.probe   = lamp_board_probe,
	.remove  = lamp_board_remove,
};

#if 0   // this make module unable to unload
// lsmod lamp_board 3395 0 [permanent], Live 0x00000000 (O)
module_platform_driver(lamp_board_driver);
#else
static int __init lamp_board_init(void)
{
	pr_info("Register lamp_board driver init\n");
	if (platform_driver_register(&lamp_board_driver)) {
		pr_err("lamp_board: failed to register disp driver\n");
		return -ENODEV;
	}
	return 0;
}

static void __exit lamp_board_exit(void)
{

	platform_driver_unregister(&lamp_board_driver);
	pr_info("Unregister lamp_board driver done\n");
}
module_init(lamp_board_init);
module_exit(lamp_board_exit);
#endif

MODULE_AUTHOR("tj<tj_wust@163.com>");
MODULE_LICENSE("GPL");
